package game;

import java.util.Random;

import be.kuleuven.cs.som.annotate.*;

import values.Vector;

/**
 * A class of asteroids, extending SpaceObject, involving a position, velocity, radius, world, mass and maxVelocity.
 * @author JeroenGillis && ThomasProvoost
 */
public class Asteroid extends SpaceObject {

	public static final double DENSITY = 2.65*Math.pow(10, 12);
	private final Random velocityDirectionGenerator;
	
	/**
	 * Create a new asteroid with the given x position, y position, x velocity, y velocity, radius, maximum velocity and world.
	 * @post	| (new this).getVelocityDirectionGenerator() == new Random()
	 * @effect 	| super(xPosition, yPosition, xVelocity, yVelocity, radius, maxVelocity,world)
	 */
	public Asteroid(double xPosition, double yPosition, double xVelocity, double yVelocity, double radius, double maxVelocity, World world)
		throws IllegalArgumentException {
			super(xPosition, yPosition, xVelocity, yVelocity, radius, maxVelocity, world, calculateMass(radius));
			this.velocityDirectionGenerator = new Random();
		
	}
	
	/**
	 * Create a new asteroid with the given x position, y position, x velocity, y velocity, radius, maximum velocity, world and random generator.
	 * @post	(new this).getVelocityDirectionGenerator() == random
	 * @effect	| super(xPosition, yPosition, xVelocity, yVelocity, radius, maxVelocity, world)
	 */
	public Asteroid(double x, double y, double xVelocity, double yVelocity,
			double radius, double maxVelocity, World world, Random random) throws IllegalArgumentException {
		super(x, y, xVelocity, yVelocity, radius, maxVelocity, world,calculateMass(radius));
		this.velocityDirectionGenerator = random;
	}

	/**
	 * A class method calculating the mass of an asteroid with given radius.
	 * @param radius
	 * 			The radius of the asteroid.
	 * @return	| result == 4.0/3.0*Math.pow(radius, 3)*Math.PI*Asteroid.DENSITY
	 */
	public static double calculateMass(double radius) {
		return 4.0/3.0*Math.pow(radius, 3)*Math.PI*Asteroid.DENSITY;
	}
	
	/**
	 * A method to let this asteroid collide with another SpaceObject.
	 * @param	otherObject
	 * 			The SpaceObject to collide with this asteroid.
	 * @effect	| super.collideWith(otherObject)
	 * @effect	| if(otherObject instanceof Asteroid) this.bounce(otherObject)
	 * @effect	| if(otherObject instanceof Ship) otherObject.terminate()
	 * @effect	| if(otherObject instanceof Bullet)
	 * 			|		otherObject.terminate()
	 * 			|		this.terminate()
	 * @throws	IllegalArgumentException
	 * 			| if( !(otherObject instanceof Ship) && !(otheroObject instanceof Asteroid) && !(otherObject instanceof Bullet) )
	 */
	@Override
	public void collideWith(SpaceObject otherObject) throws IllegalArgumentException, IllegalStateException {
		super.collideWith(otherObject);
		if(otherObject instanceof Ship) {
			otherObject.terminate();
		}
		else if(otherObject instanceof Asteroid) {
			this.bounce((Asteroid) otherObject);
		}
		else if(otherObject instanceof Bullet) {
				otherObject.terminate();
				this.terminate();
		}
		else 
			throw new IllegalArgumentException();
	}
	
	/**
	 * This asteroid breaks into two smaller asteroids.
	 * @effect	| if(getRadius >= 30 && isInWorld())
	 * 			|	new Asteroid(getxPosition()-getRadius()/2.0, getyPosition(), newVelocity.getxComp(), newVelocity.getyComp(), getRadius()/2, getMaxVelocity(), getWorld())
	 * 			|	new Asteroid(getxPosition()+getRadius()/2.0, getyPosition(), -newVelocity.getxComp(), -newVelocity.getyComp(), getRadius()/2, getMaxVelocity(), getWorld())
	 * 			|				( newVelocity = new Vector(getVelocityLength()*1.5,getVelocityDirectionGenerator().nextDouble()*2.0*Math.PI,true) )
	 */
	public void breakIntoSmallAsteroids() {
		if(getRadius() < 30 || ! isInWorld()) return;
		Vector newVelocity = new Vector(getVelocityLength()*1.5,getVelocityDirectionGenerator().nextDouble()*2.0*Math.PI,true);
		new Asteroid(getxPosition()-getRadius()/2.0, getyPosition(), newVelocity.getxComp(), newVelocity.getyComp(), getRadius()/2, getMaxVelocity(), getWorld());
		new Asteroid(getxPosition()+getRadius()/2.0, getyPosition(), -newVelocity.getxComp(), -newVelocity.getyComp(), getRadius()/2, getMaxVelocity(), getWorld());
	}
	
	/**
	 * Return the random generator of this asteroid.
	 * @return	velocityDirectionGenerator
	 */
	@Basic @Immutable
	public Random getVelocityDirectionGenerator() {
		return this.velocityDirectionGenerator;
	}

	/**
	 * A method to terminate this asteroid.
	 * @effect	| this.breakIntoSmallAsteroids()
	 * @effect	| super.terminate()
	 */
	@Override
	public void terminate() {
		breakIntoSmallAsteroids();
		super.terminate();
	}
	
	/**
	 * This asteroid bounces off the given other asteroid.
	 * @param	otherAsteroid
	 * 			The asteroid to bounce with this asteroid.
	 * @post	Impulse before and after bounce is constant.
	 * 			| (new this).getVelocity()*this.getMass()+(new otherAsteroid).getVelocity()*otherAsteroid.getMass() == 
	 * 			| 	this.getVelocity()*this.getMass()+ otherAsteroid.getVelocity()*otherAsteroid.getMass()
	 * @post	Energy before and after bounce is constant.
	 * 			| Math.pow((new this).getVelocity(),2)*this.getMass()/2 + Math.pow((new otherAsteroid).getVelocity(),2)*otherAsteroid.getMass()/2 == 
	 * 			| 	Math.pow(this.getVelocity(),2)*this.getMass()/2 + Math.pow(otherAsteroid.getVelocity(),2)*otheAsteroid.getMass()/2
	 */
	public void bounce(Asteroid otherAsteroid) {
		Vector deltaR = (otherAsteroid.getPosition()).subtract(this.getPosition());
		Vector deltaV = (otherAsteroid.getVelocity()).subtract(this.getVelocity());
		double dVdR = deltaR.scalarProduct(deltaV);
		double sigma = this.getRadius()+otherAsteroid.getRadius();
		
		double J = 2*this.getMass()*otherAsteroid.getMass()*dVdR/(sigma*(this.getMass()+otherAsteroid.getMass()));
		double Jx = J*deltaR.getxComp()/sigma;
		double Jy = J*deltaR.getyComp()/sigma;
		
		this.setVelocity(getxVelocity()+Jx/getMass(), getyVelocity()+Jy/getMass());
		otherAsteroid.setVelocity(otherAsteroid.getxVelocity()-Jx/otherAsteroid.getMass(), otherAsteroid.getyVelocity()-Jy/otherAsteroid.getMass());
	}
}
